package com.ldr.custom.spring.v1;

import com.ldr.custom.spring.annotation.*;
import com.ldr.custom.spring.enums.ConvertStrategyEnum;
import com.ldr.custom.spring.factory.ConvertFactory;
import com.ldr.custom.spring.strategy.ConvertStrategy;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class CustomDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    private Map<String, Object> ioc = new ConcurrentHashMap<>();
    private List<String> classNames = new ArrayList<>();
    private Map<String, Method> handlerMapper = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            dispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail:" + Arrays.toString(e.getStackTrace()));
        }
    }


    public void dispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        //绝对路径
        String url = request.getRequestURI().replaceAll("/+", "/");
        //处理成相对路径
        // String contextPath = request.getContextPath();
        //System.out.println("============路径："+contextPath);
        // url=url.replaceAll(contextPath,"").replaceAll("/+","/");

        if (!this.handlerMapper.containsKey(url)) {
            response.getWriter().write("404 Not Found!!");
            return;
        }

        Method method = this.handlerMapper.get(url);
        //通过反射拿到method所在class，拿到class之后还是拿到class的名称
        String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());

        Parameter[] parameters = method.getParameters();
        Map<String, String[]> parameterMap = request.getParameterMap();

        Object[] paramValue = new Object[parameters.length];

        for (int i = 0; i < parameters.length; i++) {

            Parameter parameter = parameters[i];
            //不能用instanceof，parameterType它是不是实参，而是形参
            if (parameter.getType() == HttpServletRequest.class) {
                paramValue[i] = request;
            } else if (parameter.getType() == HttpServletResponse.class) {
                paramValue[i] = response;
            } else {

                GPRequestParam requestParam = parameter.getDeclaredAnnotation(GPRequestParam.class);
                if (parameterMap.containsKey(requestParam.value())) {
                    String[] values = parameterMap.get(requestParam.value());

                    //创建转换工厂
                    ConvertStrategy convertStrategy = ConvertFactory.getInstance().getConvertStrategy(parameter.getType());
                    if (values.length > 1) {
                        paramValue[i] = convertStrategy.convert(values);
                    } else {
                        paramValue[i] = convertStrategy.convert(values[0]);
                    }

                }

            }
        }


        Object returnObj = method.invoke(ioc.get(beanName), paramValue);
        response.getWriter().write(returnObj.toString());

    }

    /**
     * 初始化配置，这里用到模版模式
     */
    @Override
    public void init(ServletConfig config) throws ServletException {

        //1.定位资源文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.加载(扫描所有包)
        doScanner(properties.getProperty("scan-package"));
        //3.注册
        doRegister();
        //4.自动依赖注入(在SPring中通过调用getBean方法依赖注入)
        doAutowired();
        //如果是springMVC会多设计一个handlerMapping

        //将@RequestMapping中配置的url和一个Method关联上
        //以便与从浏览器获得用户输入的url以后，能够找到具体执行的Method通过反射去调用
        initHandlerMapping();
    }


    /**
     * 初始化url和Method
     */
    private void initHandlerMapping() {

        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(GPController.class)) {
                continue;
            }
            //类上面的url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                GPRequestMapping mapping = clazz.getAnnotation(GPRequestMapping.class);
                baseUrl = mapping.value();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(GPRequestMapping.class)) {
                    GPRequestMapping methodMapping = method.getDeclaredAnnotation(GPRequestMapping.class);
                    String url = (baseUrl + "/" + methodMapping.value()).replaceAll("/+", "/");

                    this.handlerMapper.put(url, method);

                }
            }
        }

    }

    private void doAutowired() {

        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(GPAutowried.class)) continue;

                GPAutowried autowried = field.getAnnotation(GPAutowried.class);
                String beanBean = autowried.value();
                if ("".equals(beanBean)) {
                    beanBean = field.getType().getName();
                }
                //强吻（强制访问）
                field.setAccessible(true);
                try {
                    System.out.println("value:" + entry.getValue() + "," + ioc.get(beanBean));
                    field.set(entry.getValue(), ioc.get(beanBean));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 注册
     */
    private void doRegister() {

        if (classNames.isEmpty()) return;

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(GPController.class)) {
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    //在spring中在这个阶段是不会直接put 实例话对象，这里是put是beanDefinition
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(GPService.class)) {

                    GPService gpService = clazz.getAnnotation(GPService.class);
                    //默认用类名首字母注入
                    //如果自己定义了beanName，那么优选使用自己定义的beaName
                    //如果是一个接口，使用接口的类型去自动注入

                    //在Spring中同样会分别调用不同的方法，atuowritedByName autuwritedByType
                    String beanNam = gpService.value();
                    if ("".equals(beanNam)) {
                        beanNam = lowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanNam, instance);

                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(), instance);
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 扫描包路径
     *
     * @param packageName
     */
    private void doScanner(String packageName) {

        URL resourceUrl = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));

        File classDir = new File(resourceUrl.getFile());

        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(packageName + "." + file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replaceAll(".class", ""));
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        //在Spring中是通过Reader去查找和定位对不对
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replace("classpath:", ""));

        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private String lowerFirstCase(String str) {

        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}
